<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>视频聊天</title>
  <style>
    video {
      width: 320px;
      height: 240px;
      border: 1px solid black;
    }

    div {
      display: inline-block;
    }
  </style>
</head>

<body>
  <!-- 次空白脚本转为查询参数预留 -->
  <script></script>

  <script src="https://webrtc.github.io/adapter/adapter.js" type="text/javascript"></script>

  <script type="text/javascript" src="/clientXHRSignaling.js"></script>

  <script>

    var signalingChannel, key, id,
      haveLocalMedia = false,
      weWaited = false,
      myVideoStream, myVideo,
      yourVideoStream, yourVideo,
      doNothing = function () { },
      pc, dc, data = {},
      constraints = {
        mandatory: {
          OfferToReceiveAudio: true,
          OfferToReceiveVideo: true
        }
      }

    // 自动开始获取本地媒体
    window.onload = function () {
      if (queryParams && queryParams['key']) {    // 加载空白脚本中预置的queryParams参数
        document.getElementById('key').value = queryParams['key']
        connect()
      }

      myVideo = document.getElementById('myVideo')
      yourVideo = document.getElementById('yourVideo')
      getMedia()
    }

    // 连接服务器并建立信令通道
    function connect() {
      var errorCB, scHandlers, handleMsg
      // 获取连接密钥
      key = document.getElementById('key').value
      // 处理通过信令通道收到的所有消息
      handleMsg = function (msg) {
        var msgE = document.getElementById('inmessages')
        var msgString = JSON.stringify(msg).replace(/\\r\\n/g, '\n')
        msgE.value = msgString + '\n' + msgE.value    // 将最新的消息放置在最上方
        console.log('msgString:', msgString)
        if (msg.type === 'offer') {
          pc.setRemoteDescription(new RTCSessionDescription(msg))
          answer()
        } else if (msg.type === 'answer') {
          pc.setRemoteDescription(new RTCSessionDescription(msg))
        } else if (msg.type === 'candidate') {
          pc.addIceCandidate(new RTCIceCandidate({
            sdpMLineIndex: msg.mlineindex,
            candidate: msg.candidate
          }))
        }
      }
      scHandlers = {
        'onWaiting': function () {
          setStatus('Waiting')
          weWaited = true
        },
        'onConnected': function () {
          setStatus('Connected')
          // 已成功连接，开始建立对等连接
          createPC()
        },
        'onMessage': handleMsg
      }
      // 创建信令通道
      signalingChannel = createSignalingChannel(key, scHandlers)
      errorCB = function (msg) {
        document.getElementById('response').innerHTML = msg
      }

      // 进行连接
      signalingChannel.connect(errorCB)
    }

    // 通过信道发送消息
    function send(msg) {
      var handler = function (res) {
        document.getElementById('response').innerHTML = res
        return
      }
      msg = msg || document.getElementById('message').value   // 没有消息则获取信道消息
      // 发布到屏幕上
      msgE = document.getElementById('outmessages')
      var msgString = JSON.stringify(msg).replace(/\\r\\n/g, '\n')
      msgE.value = msgString + '\n' + msgE.value
      // 通过信令通道发送出去
      signalingChannel.send(msg, handler)
    }

    // 获取本地媒体
    function getMedia() {
      (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia)({
        audio: true,
        video: true
      }, gotUserMedia, didntGetUserMedia)
    }
    function didntGetUserMedia() {
      console.log('cound not get user media')
    }
    function gotUserMedia(stream) {
      myVideoStream = stream
      haveLocalMedia = true
      // 向我显示我的本地视频
      myVideo.srcObject = myVideoStream
      // 等待pc创建完毕
      attachMediaIfReady()
    }

    // 创建对等连接，即实例化peerConnection
    function createPC() {
      var stunUri = true,
        turnUri = false,
        myfalse = function (v) {
          return ((v === '0') || (v === 'false') || (!v))
        },
        config = new Array();
      // 基于各个查询参数调整配置字符串
      if (queryParams) {
        if ('stunUri' in queryParams) {
          stunUri = !myfalse(queryParams['stunUri'])
        }
        if ('turnUri' in queryParams) {
          turnUri = !myfalse(queryParams['turnUri'])
        }
      }
      if (stunUri) {
        config.push({
          url: 'stun:stunserver.org'
        })
      }
      if (turnUri) {
        if (stunUri) {
          config.push({
            url: 'turn:user@turn.webrtcbook.com',
            credential: 'test'
          })
        } else {
          config.push({
            url: 'turn:user@turn-only.webrtcbook.com',
            credential: 'test'
          })
        }
      }
      console.log('config: ', JSON.stringify(config))
      pc = new RTCPeerConnection({
        iceServers: config
      })
      pc.onicecandidate = onIceCandidate
      pc.onaddstream = onRemoteStreamAdded
      pc.onremovestream = onRemoteStreamRemoved
      pc.ondatachannel = onDataChannelAdded
      // 等待媒体就绪
      attachMediaIfReady()
    }

    // 检测到浏览器一端创建了数据通道后，则调用此程序：保存数据通道，设置处理程序
    function onDataChannelAdded(e) {
      dc = e.channel
      setupDataHandlers()   // 设置数据通道处理程序
      sendChat('Hello!')    // 发送hello
    }

    // 设置数据通道消息的处理程序
    function setupDataHandlers() {
      data.send = function (msg) {
        msg = JSON.stringify(msg)
        console.log('sending msg ：' + msg + ' over data channel')
        dc.send(msg)
      }
      dc.onmessage = function(e) {
        var msg = JSON.parse(e.data),
          cb = document.getElementById('chatbox'),
          rtt = document.getElementById('rtt');
        if (msg.rtt) {    // 如果是实时文本，显示在实时窗口
          console.log('received rtt of ' + msg.rtt)
          rtt.value = msg.rtt
          msg = msg.rtt
        } else if (msg.chat) {    // 如果是完整的消息，显示在聊天窗口
          console.log('received chat of ' + msg.chat)
          cb.value += '<- ' + msg.chat + '\n'
          rtt.value = ''
          msg = msg.chat
        } else {
          console.log('received ' + msg + ' on data channel')
        }
      }
    }

    // 发送实时文本
    function sendRtt(){
      var msg = document.getElementById('chat').value
      data.send({
        rtt: msg
      })
    }

    // 发送常规聊天文本
    function sendChat(msg) {
      var cb = document.getElementById('chatbox'),
          c = document.getElementById('chat');
      
      // 在本地显示消息，发送消息并强制窗口滚动到最后一行
      msg = msg || c.value
      console.log('sendChat-> ', msg)
      cb.value += '-> ' + msg + '\n'
      data.send({
        chat: msg
      })
      c.value = ''
      cb.scrollTop = cb.scrollHeight    // 滚动底部
    }


    // 如果当前浏览器有另一个候选项，将其发送给对等端
    function onIceCandidate(e) {
      if (e.candidate) {
        send({
          type: 'candidate',
          mlineindex: e.candidate.sdpMLineIndex,
          candidate: e.candidate.candidate
        })
      }
    }

    // 如果我们浏览器检测到另一端加入了媒体流，则将其显示在屏幕上
    function onRemoteStreamAdded(e) {
      yourVideoStream = e.stream
      console.log('yourVideo: ', yourVideo)
      yourVideo.srcObject = yourVideoStream
      setStatus('On call')
    }

    // 远端移除流，这里不做操作
    function onRemoteStreamRemoved(e) {

    }

    function attachMediaIfReady() {
      if (pc && haveLocalMedia) attachMedia()
    }

    // 将本地流添加至对等连接
    function attachMedia() {
      pc.addStream(myVideoStream)
      setStatus('Ready for call')
      if (queryParams && queryParams['call'] && !weWaited) {
        call()
      }
    }

    // 生成一个offer
    function call() {
      dc = pc.createDataChannel('chat')
      setupDataHandlers()
      pc.createOffer(gotDescription, doNothing, constraints)
    }

    // 应答会话描述，生成answer
    function answer() {
      pc.createAnswer(gotDescription, doNothing, constraints)
    }

    // 一旦获取到了会话描述，就将其作为本地描述，然后将其发送至另一端的浏览器
    function gotDescription(localDesc) {
      pc.setLocalDescription(localDesc)
      send(localDesc)
    }

    // 进度操作
    function setStatus(str) {
      var statuslineE = document.getElementById('statusline'),
        statusE = document.getElementById('status'),
        sendE = document.getElementById('send'),
        connectE = document.getElementById('connect'),
        callE = document.getElementById('call'),
        scMessageE = document.getElementById('scMessage');
      switch (str) {
        case 'Waiting':
          statuslineE.style.display = 'inline'
          statusE.innerHTML = '等待对等连接...'
          sendE.style.display = 'none'
          connectE.style.display = 'none'
          break
        case 'Connected':
          statuslineE.style.display = 'inline'
          statusE.innerHTML = '对等信道已连接，等待本地媒体....'
          sendE.style.display = 'inline'
          connectE.style.display = 'none'
          scMessageE.style.display = 'inline-block'
          break
        case 'Ready for call':
          statusE.innerHTML = '准备呼叫...'
          callE.style.display = 'inline'
          break
        case 'On call':
          statusE.innerHTML = 'On call'
          callE.style.display = 'none'
          break
        default:
      }
    }

  </script>

  <div id="setup">
    <p>WebRTC 视频聊天demo测试</p>
    <p>
      Key:
      <input type="text" name="key" id="key" onkeyup="if (event.keyCode == 13) {connect();return false;}" />
      <button id="connect" onclick="connect()">连接</button>
      <span id="statusline" style="display:none">状态：
        <span id="status">已断开</span>
      </span>
      <button id="call" style="display:none" onclick="call()">呼叫</button>
    </p>
  </div>

  <div id="scMessage" style="float:right;display:none">
    <p>信道消息：
      <input type="text" width="100%" name="message" id="message" onkeyup="if (event.keyCode == 13){send();return false;}" />
      <button id="send" style="display:none" onclick="send()">发送</button>
    </p>

    <p>响应：
      <span id="response"></span>
    </p>
  </div>

  <br/>

  <div style="width:30%;vertical-align:top">
    <div>
      <video autoplay="autoplay" id="myVideo" controls muted="true" />
    </div>
    <p>
      <b>发出的消息</b>
      <br/>
      <textarea id="outmessages" style="width:100%" rows="30" cols="30"></textarea>
    </p>
  </div>

  <div style="width: 30%;vertical-align:top">
    <textarea name="" id="chatbox" style="width:100%" rows="10"></textarea>
    <p style="width:100%">
      <b>实时消息：</b>
      <textarea name="" id="rtt" style="width:100%" rows="10"></textarea>
    </p>
    <p style="width:100%">
      <p>聊天消息：</p>
      <input type="text" style="width:100%;" name="chat" id="chat" onkeyup="sendRtt();if (event.keyCode == 13){sendChat();return false;}"/>
    </p>
  </div>

  <div style="width:30%;vertical-align:top;">
    <div>
      <video id="yourVideo" autoplay="autoplay" controls muted="true"/>
    </div>
    <p>
      <b>收到的消息</b>
      <br/>
      <textarea id="inmessages" style="width:100%" rows="30" cols="30"></textarea>
    </p>
  </div>

</body>

</html>